Este é o primeiro de 2 artigos voltados para engenheiros de sistemas embarcados que desejam iniciar a elaboração de projetos baseados em RTOS, utilizando especificamente o FreeRTOS¹.
Ao longo deste, mostrarei como começar com uma demo existente e modificá-la para um microcontrolador desejado (target) e, em seguida, a implementação de uma aplicação que utiliza a biblioteca coreMQTT, uma das bibliotecas de conectividade disponíveis na base de código do FreeRTOS.
Esse processo de iniciar com uma demonstração existente e modificá-la para atender necessidades específicas de HW e SW de um determinado projeto é de fato o método recomendado pelo FreeRTOS, como indicado aqui².
Hardware Utilizado
O hardware escolhido para o desenvolvimento deste projeto consiste em um Arduino UNO R3³ e um ESP-01⁴. O Arduino UNO R3 utiliza o microcontrolador ATMega328P da Atmel (atual Microchip), no qual o FreeRTOS será executado. Já o ESP-01, baseado no ESP8266, atuará como módulo de conectividade, para que possamos nos conectar ao servidor (broker) MQTT.
Estes dispositivos foram escolhidos por encontrarem-se disponíveis para mim no momento da elaboração deste artigo, além do que, o projeto FreeRTOS disponibiliza uma demo⁵ para o ATMega323 que utiliza a toolchain WinAVR⁶, baseado no GNU GCC compiler para AVR⁷ o qual utilizaremos aqui. A existência de uma demo oficial utilizando a mesma arquitetura de CPU e compilador que o microcontrolador alvo facilita muito o port para o target, o que demonstro passo a passo a seguir.
Por fim, o Arduino UNO R3 é uma placa comumente encontrada nas culturas “makers” e “DIYs” e de custo acessível. Contudo, o método que demonstro aqui é aplicável não somente ao Arduino UNO R3 como também para outras famílias de microcontroladores. A lista dos dispositivos e toolchains suportados oficialmente pelo projeto FreeRTOS pode ser encontrada aqui⁸.
Clonando o projeto FreeRTOS
Para começar, devemos clonar o projeto FreeRTOS disponível em https://github.com/FreeRTOS/FreeRTOS/. Utilizaremos a tag 202411.00, que disponibiliza o mesmo kernel e bibliotecas da versão 202406-LTS.O projeto FreeRTOS mantém suas bibliotecas em diferentes repositórios no GitHub, e para disponibilizar uma árvore de desenvolvimento completa, a função de git submodules⁹ é utilizada. Para clonarmos corretamente todo o projeto e suas dependências, devemos utilizar o seguinte comando:
$ git clone -b 202411.00 https://github.com/FreeRTOS/FreeRTOS.git --recurse-submodulesEsse processo irá obter todo o código presente em https://github.com/FreeRTOS/FreeRTOS/ como também dos outros repositórios necessários, e portanto, pode levar alguns minutos para ser concluído.
Crie um diretório vazio e dentro dele rode o comando acima para clonar a base de código do projeto FreeRTOS. No meu caso, utilizarei o diretório ~/workspace/aprendizado. Após terminado, teremos um diretório chamado FreeRTOS com o seguinte conteúdo:
Compilando a demo existente
A demonstração que utilizaremos como base reside em FreeRTOS/FreeRTOS/Demo/AVR_ATMega323_WinAVR/. Dentro deste diretório existe o código-fonte da aplicação main.c, o header de configuração necessário a todas as aplicações que utilizam FreeRTOS FreeRTOSConfig.h, o código-fonte de outras partes do sistemas necessários ao funcionamento da aplicação, e um makefile responsável por compilar o código-fonte e gerar a imagem final para ser gravada no target.
Note que o código da aplicação é relativamente simples e uma pessoa familiarizada com C não deverá ter dificuldades em compreendê-lo. Essa é mais uma vantagem ao se utilizar o FreeRTOS, desenvolver aplicações utilizando o kernel do FreeRTOS é extremamente simples e direto.
O próximo passo é voltar ao diretório de trabalho e instalar a toolchain disponibilizada em (7).
Baixe a toolchain AVR 8-Bit compatível com seu sistema operacional. No meu caso, utilizo a toolchain AVR 8-Bit para Linux. Após descomprimir o pacote no meu diretório de trabalho, renomeio o diretório da toolchain de avr8-gnu-toolchain-linux_x86_64 para avr-toolchain, de modo a facilitar futuras referências a esse caminho.
Verifique que em avr-toolchain/bin você possui as ferramentas necessárias a compilação cruzada de código para arquitetura AVR:
Adicione este diretório em sua variável $PATH executando o comando a seguir (caso necessário, adapte o comando o seu ambiente):
export PATH=~/workspace/aprendizado/avr-toolchain/bin:$PATHAqui estamos prontos para compilar a aplicação de demonstração para o ATMega323.
Retorne ao diretório da demo –FreeRTOS/FreeRTOS/Demo/AVR_ATMega323_WinAVR – e entre com o comando make (ou make all) e a compilação deverá concluir sem erros:
Caso você encontre algum erro, reveja os passos anteriores, e confirme que os caminhos e toolchain estão de acordo com o seu ambiente de desenvolvimento.
Se tudo ocorreu bem, você deve visualizar a mensagem Errors: none e também os arquivos rtosdemo.xxx conforme ilustrado acima.
O arquivo rtosdemo.hex corresponde a imagem que pode ser gravada no MCU ATMega323 utilizando um programador compatível. Contudo, nosso objetivo é gerar a mesma aplicação de demonstração, mas para o ATMega328P, o MCU presente no HW do Arduino UNO R3.
Execute make clean para limpar os arquivos gerados pelo processo de compilação.
Portando a demo para o ATMega328P
No final desta seção teremos as modificações necessárias para a aplicação de demonstração rodar em um ATMega328P. Mas primeiro precisamos discutir com mais detalhes cada um dos arquivos do código-fonte para determinar quais modificações são necessárias.
Não faça as alterações manualmente no código-fonte da aplicação enquanto lê o artigo. No início da sessão seguinte, eu disponibilizo um repositório contendo todas as mudanças necessárias.
FreeRTOSConfig.h
Todo projeto que utiliza o FreeRTOS deve definir um header de configuração chamado FreeRTOSConfig.h. Na imagem abaixo, vemos as configurações para demo do ATMega323:
De cara, vemos que a primeira alteração que devemos fazer refere-se à configuração do clock de CPU. Para o ATMega323, o valor corresponde a 8MHz, e no hardware do Arduino UNO R3, o valor do clock utilizado é de 16MHz. Fora isso, vamos manter todos os outros valores como estão. A definição de cada parâmetro pode ser encontrada aqui¹⁰.
main.c
Em main.c está definida a aplicação principal, a qual encontra-se detalhadamente comentada. Em resumo, a aplicação consiste em inicializar o HW com a chamada a vParTestInitialise():
registrar (ou “criar”) algumas tarefas para serem executadas:
E iniciar o scheduler do FreeRTOS:
O código-fonte da maioria das tarefas executadas nessa aplicação pode ser encontrado em FreeRTOS/FreeRTOS/Demo/Common/Minimal e os respectivos headers em FreeRTOS/FreeRTOS/Demo/Common/include.
Nosso objetivo aqui não é discutir em detalhes o funcionamento das tarefas e do escalonador no FreeRTOS, mas sim focar no port de um MCU para o outro. Ainda assim, é importante ter um entendimento de alto nível de como as tarefas funcionam. Em resumo, elas são definidas tendo uma função como ponto de entrada e uma prioridade de execução e, a princípio, elas não retornam, ou seja, elas devem executar em loop infinito. O escalonador é responsável pela execução e troca de contexto entre as diferentes tarefas de acordo com a prioridade com a qual cada tarefa foi registrada. Uma vez chamado vTaskStartScheduler(), o escalonador inicia a execução das tarefas e essa função não retorna.
Para saber mais sobre tarefas, prioridades e outros recursos importantes ao FreeRTOS, recomendo a leitura deste artigo¹¹ do Pedro Bertoleti além da documentação¹² oficial do projeto FreeRTOS.
Em alto nível, essa aplicação de exemplo executa algumas tarefas que utilizam recursos de CPU e memória (vStartIntegerMathTasks, vStartPolledQueueTasks, vStartRegTestTasks) e hardware (vAltStartComTestTasks), e caso nenhum erro seja detectado, uma porta de saída digital tem o seu nível lógico alterado:
Caso um LED for conectado a esta porta digital, ele proverá um feedback visual de que a aplicação está executando sem erros. Por fim, co-rotinas para piscar 3 LEDs também são criadas e, caso LEDs sejam ligados a determinadas portas digitais, o usuário também terá feedback visual sobre a execução das co-rotinas de LED.
É importante notar que de modo geral, o código de uma aplicação é completamente independente do HW em que a mesma está sendo executada, e por isso não precisamos fazer nenhuma alteração em main.c
makefile
Como estamos usando uma toolchain que é compatível entre ATMega323 e ATMega328P, a única mudança que precisamos fazer no makefile é na linha 34, na variável MCU:
diff --git a/makefile b/makefile
index 875aa78..3feb547 100644
--- a/makefile
+++ b/makefile
@@ -31,7 +31,7 @@
# MCU name
-MCU = atmega323
+MCU = atmega328p^M
# Output format. (can be srec, ihex, binary)
FORMAT = ihexE podemos manter o restante como está.
Drivers de IO e Comunicação Serial
Os arquivos PartTest/ParTest.c e serial/serial.c implementam respectivamente um driver de output digital e um driver de comunicação serial utilizando a UART do MCU. Alterações no código fonte desses arquivos são necessárias visando o HW em que vamos executar a aplicação. Essas alterações são importantes para que a aplicação de demonstração funcione corretamente, uma vez que algumas tarefas utilizadas necessitam das interfaces implementadas em ParTest.c e serial.c. Além disso, também faremos uso desses drivers na nossa aplicação principal.
Nesse ponto, é muito importante a consulta dos datasheets dos microcontroladores para que se possa entender quais portas, registradores e interrupções serão precisos configurar. Outro material de consulta importante são os headers de IO do MCU presentes na toolchain. Eles podem ser encontrados em avr-toolchain/avr/include/avr. Para o ATMega328P, o arquivo de interesse é iom328p.h. Ele contém as macros utilizadas nas configurações de IO, registradores e interrupções. Não irei detalhar as mudanças necessárias para a implementação da aplicação no Arduino UNO R3, mas elas estarão disponibilizadas no repositório compartilhado na próxima sessão. O leitor interessado pode verificar as alterações que tiveram que ser feitas.
port.c
Até aqui verificamos apenas o código fonte da aplicação, disponível em FreeRTOS/FreeRTOS/Demo/AVR_ATMega323_WinAVR. Em nenhum momento averiguamos o código do kernel do FreeRTOS em si. O kernel FreeRTOS é implementado em 3 arquivos fontes, tasks.c, list.c e queue.c, localizados em FreeRTOS/FreeRTOS/Source, e utilizados por qualquer projeto. Dependendo dos recursos empregados em uma aplicação, outros arquivos fontes como timers.c ou croutine.c podem ser necessários¹³.
O código do kernel é implementado de modo a ser independente de qualquer solução de HW, MCU e toolchain que possa ser utilizado, mas para ele funcionar, é necessária a implementação de uma camada de abstração que é dependente da solução utilizada. O código dependente encontra-se em FreeRTOS/Source/portable/[compiler]/[architecture]. No caso do ATMega323, esse caminho corresponde a FreeRTOS/Source/portable/GCC/ATMega323. No nosso port para o ATMega328P, eu suponho que a camada de abstração existente para o ATMega323 será compatível com o ATMega328P, salvo pequenas alterações que demonstro a seguir.
De modo geral, o código do port é responsável por inicializar o stack do kernel, salvar e restaurar os estados dos registradores relevantes e stack de cada tarefa para a troca de contexto entre elas, e gerar o “tick”. O “tick” é uma interrupção gerada com período constante. Ele é utilizado no FreeRTOS para medir intervalos de tempo e pelo escalonador para realizar as trocas de tarefas.
No caso do ATMega323, é utilizado o Timer/Contador 1 do MCU para geração do tick. Para o ATMega328P, também utilizaremos o Timer/Contador 1 para gerar o tick, mas precisamos adaptar o código em port.c para utilizar os registradores e bitmaks compatíveis com o ATMega328P. Novamente consultar o datasheet de ambos os microcontroladores se faz necessário, e as seguintes alterações devem ser implementadas:
diff --git a/portable/GCC/ATMega323/port.c b/portable/GCC/ATMega323/port.c
index 98f0d56..26ea4f3 100644
--- a/portable/GCC/ATMega323/port.c
+++ b/portable/GCC/ATMega323/port.c
@@ -52,7 +52,7 @@
#define portCLEAR_COUNTER_ON_MATCH ( ( uint8_t ) 0x08 )
#define portPRESCALE_64 ( ( uint8_t ) 0x03 )
#define portCLOCK_PRESCALER ( ( uint32_t ) 64 )
-#define portCOMPARE_MATCH_A_INTERRUPT_ENABLE ( ( uint8_t ) 0x10 )
+#define portCOMPARE_MATCH_A_INTERRUPT_ENABLE ( ( uint8_t ) 0x02 )
/*-----------------------------------------------------------*/
@@ -394,9 +394,9 @@ static void prvSetupTimerInterrupt( void )
/* Enable the interrupt - this is okay as interrupt are currently globally
* disabled. */
- ucLowByte = TIMSK;
+ ucLowByte = TIMSK1;
ucLowByte |= portCOMPARE_MATCH_A_INTERRUPT_ENABLE;
- TIMSK = ucLowByte;
+ TIMSK1 = ucLowByte;
}
/*-----------------------------------------------------------*/Com isso, estamos finalmente aptos a gerar a aplicação de demonstração para o ATMega328P.
Compilando e Rodando a Demo no ATMega328P
Primeiro vamos clonar o código com o port para o ATMega328P.
Em FreeRTOS/FreeRTOS rode o seguinte comando:
$ git clone -b portFrom_atmega323 https://github.com/vinRocha/ATMega328P_FreeRTOS.git AVR_ATMega328P_GCCApós isso, você deverá ter o diretório FreeRTOS/FreeRTOS/AVR_ATMega328P_GCC com o seguinte conteúdo:
O conteúdo do diretório se assemelha com o que observamos em FreeRTOS/FreeRTOS/Demo/AVR_ATMega323_WinAVR, com exceção do diretório portable, que contém as alterações necessárias em port.c para utilização do ATMega328P. Idealmente, esse código residiria em FreeRTOS/Source/portable/GCC/ATMega328P, mas nesse exemplo, deixo tudo junto com o código da aplicação para facilitar o acompanhamento do leitor.
Aqui, como na seção 3, o leitor deve ter configurado a variável$PATH com caminho da toolchain instalada em seu ambiente. Após isso, basta rodar make ou make all para gerar a imagem final rtosdemo.hex.
Para gravarmos a imagem no MCU, utilizaremos o gravador disponível com o Arduino IDE.
Conecte o Arduino UNO R3 em seu computador e abra o Arduino IDE
Navegue em File -> Preferences e confirme que Show verbose output during upload encontra-se selecionado:
Agora faça o upload do projeto vazio (ou qualquer outro projeto) para que na seção ‘Output’ possamos ver o comando necessário para gravar uma imagem no Arduino UNO R3. Corra as mensagens para cima até que você encontre o início do comando do avrdude:
no meu caso, o comando utilizado foi:
"/home/vsilva1/.arduino15/packages/arduino/tools/avrdude/6.3.0-arduino17/bin/avrdude" "-C/home/vsilva1/.arduino15/packages/arduino/tools/avrdude/6.3.0-arduino17/etc/avrdude.conf" -v -V -patmega328p -carduino "-P/dev/ttyACM0" -b115200 -D "-Uflash:w:/home/vsilva1/.cache/arduino/sketches/8AC1A70F1160CAB1BDCB29CB1439B1BF/empty.ino.ino.hex:i"Agora basta substituirmos no final do comando acima o nome da imagem a ser gravada, o que para mim resulta em:
"/home/vsilva1/.arduino15/packages/arduino/tools/avrdude/6.3.0-arduino17/bin/avrdude" "-C/home/vsilva1/.arduino15/packages/arduino/tools/avrdude/6.3.0-arduino17/etc/avrdude.conf" -v -V -patmega328p -carduino "-P/dev/ttyACM0" -b115200 -D "-Uflash:w: rtosdemo.hex :i"Após rodar esse comando dentro do diretório FreeRTOS/FreeRTOS/AVR_ATMega328P_GCC, temos o Arduino UNO R3 gravado com a imagem do FreeRTOS que acabamos de compilar:

A tarefa ComTestTasks da aplicação de demonstração precisa que o pino serial TX do dispositivo seja conectado ao RX, de modo que a tarefa possa “ler” de volta os valores que estão sendo transmitidos. Contudo, não deixe o TX conectado ao RX durante a gravação da imagem, pois ela irá falhar. Deixe o TX flutuando, e somente após a gravação ser concluída com sucesso, conecte o TX ao RX e então reset o MCU utilizando o push button, conforme ilustrado acima.
O LED de status das tarefas, ou seja, que indica que as tarefas continuam a executar sem erro é o LED built-in da placa Arduino UNO R3, indicado pela letra L no PCB. Se tudo estiver bem, esse LED deverá alterar de estado a cada 3 segundos (customizável em main.c). As co-rotinas de LED utilizam as portas digitais 2, 3, e 4 do Arduino UNO R3. Caso você conecte LEDs a essas portas, você terá feedback visual de que as co-rotinas estão em execução.
Para maiores detalhes sobre a aplicação de demonstração, verifique o código-fonte de cada tarefa.
Conclusão
Vimos nesse artigo o passo a passo de como começar com o código-fonte do FreeRTOS e realizar as modificações necessárias para implementação em um microcontrolador, dado que esse microcontrolador seja da mesma arquitetura de um MCU já suportado. Ao longo do port, nos familiarizamos com a organização do código-fonte e com a filosofia de funcionamento do FreeRTOS, e por fim geramos a aplicação de demonstração compatível com o HW que tínhamos em mãos.
Esse primeiro artigo realiza ao leitor uma pequena introdução ao mundo do FreeRTOS e fornece as referências necessárias para que o mesmo aprofunde seu conhecimento. Contudo, realizar esse estudo de posse de um hardware onde se possa colocar em prática os conhecimentos adquiridos é fundamental para a real absorção dos mesmos.
Observe que esse trabalho não visa prover um port do FreeRTOS para o ATMega328P. O ATMega328P apenas era o MCU disponível para mim no momento de escrita deste artigo. De fato, já existe um port do FreeRTOS para o ATMega328P e outros MCUs da família AVR-ATMega. Esse port encontra-se disponível em https://github.com/feilipu/Arduino_FreeRTOS_Library ¹⁴ e nas bibliotecas do Arduino IDE. O port também pode ser encontrado em FreeRTOS/FreeRTOS/Source/portable/ThirdParty/GCC/ATmega, o que eu recomendo fortemente que seja utilizado ao invés do port fornecido nesse artigo, pois o port do Phillip Stevens é melhor testado e suportado.
No artigo a seguir, veremos como utilizar a biblioteca coreMQTT disponível no código-fonte do FreeRTOS para criar uma aplicação de IoT.
Referências
- https://freertos.org
- https://freertos.org/Documentation/01-FreeRTOS-quick-start/01-Beginners-guide/02-Quick-start-guide
- https://docs.arduino.cc/hardware/uno-rev3/
- https://docs.ai-thinker.com/en/esp8266
- https://freertos.org/Documentation/02-Kernel/03-Supported-devices/04-Demos/Atmel-now-Microchip/Atmel_AVR_Mega_AVR
- https://winavr.sourceforge.net/
- https://www.microchip.com/en-us/tools-resources/develop/microchip-studio/gcc-compilers
- https://freertos.org/Documentation/02-Kernel/03-Supported-devices/00-Supported-devices
- https://git-scm.com/book/en/v2/Git-Tools-Submodules
- https://www.freertos.org/Documentation/02-Kernel/03-Supported-devices/02-Customization
- https://embarcados.com.br/rtos-para-iniciantes-com-arduino-e-freertos/
- https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/01-Tasks-and-co-routines/00-Tasks-and-co-routines
- https://www.freertos.org/Documentation/02-Kernel/06-Coding-guidelines/01-Source-code-organization
- https://github.com/feilipu/Arduino_FreeRTOS_Library





